function AutoCompleter(control, updateCallback, updateDelay) {
	this.control = control;
	this.updateCallback = updateCallback;
	if(updateDelay == undefined) updateDelay = 300;
	this.updateDelay = 300;
	this.selectedIx = -1;
	this.lastKeys = "";
	this.ulList = null;
	this.hasFocus = this.control.selectionStart != this.control.selectionEnd;
	this.updateTimer = 0;
	
	// event handlers
	this.events = {
		txt_onKey:this.txt_onKey.bindAsEventListener(this),
		txt_onFocus:this.txt_onFocus.bindAsEventListener(this),
		txt_onBlur:this.txt_onBlur.bindAsEventListener(this)
	}
	this.control.setAttribute("autocomplete", "off");
	this.ulList = document.createElement("ul");
	this.ulList.className = "suggest";
	this.ulList.style.width = this.control.offsetWidth + "px";
	this.ulList.style.display = "none";
	this.ulList.style.position = "absolute";
	this.control.parentNode.insertBefore(this.ulList, this.control.nextSibling);
	Event.observe(this.control, "keyup", this.events.txt_onKey);
	Event.observe(this.control, "focus", this.events.txt_onFocus);
	Event.observe(this.control, "blur", this.events.txt_onBlur);
}

AutoCompleter.prototype.openItems = function() {
	Position.clone(this.control, this.ulList, {
		setHeight: false, 
		offsetTop: this.control.offsetHeight
	});
	this.ulList.style.display = "block";
}

AutoCompleter.prototype.closeItems = function() {
	this.ulList.style.display = "none";
}

AutoCompleter.prototype.bindItems = function(items) {	
	this.data = items;
	this.hasFocus = this.control.selectionStart != this.control.selectionEnd;
	//if(!this.hasFocus) return;
	while(this.ulList.childNodes.length > 0) this.ulList.removeChild( this.ulList.childNodes[0] );

	if(this.lastKeys != "") this.suggestItem();
	
	for(var i=0; i<items.length; i++) {
		var li = document.createElement("li");
		var label = document.createElement("span");
		label.appendChild( document.createTextNode(items[i] ));
		li.appendChild( label );
		if(this.selectedIx == i) li.className = "selected";
		this.ulList.appendChild(li);
	}
	//ulSuggest.className = (items.length == 0) ? "off":"";
	if(items.length == 0 || this.lastKeys == "") {
		this.closeItems();
	} else {
		this.openItems();
		
	}
}

AutoCompleter.prototype.suggestItem = function() {
	var targetItem = this.getAproxItem(this.control.value.substring(0, this.control.selectionStart));
	if(targetItem != null) {
		var sCur = this.control.value.substring(0, this.control.selectionStart);
		var sNew = targetItem.txt;
		sNew = sCur + sNew.substring(sCur.length);
		this.control.value = sNew;
		this.selectRange(sCur.length,sNew.length);
		this.selectedIx = targetItem.ix;
	} else {
		this.selectedIx = -1;
	}
}

AutoCompleter.prototype.selectRange = function(iStart, iLength) {
    if (this.control.createTextRange) {
        var oRange = this.control.createTextRange();
        oRange.moveStart("character", iStart);
        oRange.moveEnd("character", iLength - this.control.value.length);
        oRange.select();
    } else if (this.control.setSelectionRange) {
        this.control.setSelectionRange(iStart, iLength);
    }
    this.control.focus();
}

AutoCompleter.prototype.getAproxItem = function(key) {
	if(this.data == null) return null;
	key = key.toLowerCase();
	for(var i=0; i<this.data.length; i++) {
		if(this.data[i].toLowerCase().indexOf(key) == 0) return {txt:this.data[i].toLowerCase(),ix:i};
	}
	return null;
}


AutoCompleter.prototype.txt_onKey = function(e) {
	var oldIndex = this.selectedIx;
	var commitChange = false;
	if(e.keyCode == 13) {
		// enter
		commitChange = true;
		this.closeItems();
	} else if(e.keyCode == 40) {
		// down
		commitChange = true;
		this.selectedIx++;
		this.openItems();
	} else if(e.keyCode == 38) {
		// up
		commitChange = true;
		this.selectedIx--;
		this.openItems();
	} else if(e.keyCode > 31 || e.keyCode == 8) {
		// some key (any key?)
		var keys = this.control.value;
		clearInterval(this.updateTimer);
		if(this.lastKeys != keys && keys != "") {
			// this.suggestItem();
			// TODO: call updatefunction (w timer)
			if(this.getAproxItem(keys) == null) this.closeItems();
			var callUpdate = this.callUpdate.bind(this);
			this.updateTimer = window.setInterval(callUpdate, this.updateDelay);
			
		}
		this.lastKeys = keys;
		if(keys == "") this.closeItems();
	}
	if(oldIndex >= 0 && this.ulList.childNodes[oldIndex]) this.ulList.childNodes[oldIndex].className = "";
	var maxIndex = this.ulList.childNodes.length-1;
	// TODO: validate that current ix EQUALS current text
	if(this.selectedIx < -1) this.selectedIx = -1;
	if(this.selectedIx > maxIndex) this.selectedIx = maxIndex;
	if(this.selectedIx >= 0) {
		var data = this.data[this.selectedIx];
		if(data != null) {
			this.ulList.childNodes[this.selectedIx].className = "selected";
			if(commitChange) {
				this.control.value = data;
				this.lastKeys = data;
			}
		}
	}
}

AutoCompleter.prototype.callUpdate = function() {
	clearInterval(this.updateTimer);
	this.updateCallback.call(this, {autoCompleter:this, keys:this.lastKeys});
}

AutoCompleter.prototype.txt_onFocus = function(e) {
	this.hasFocus = true;
}
AutoCompleter.prototype.txt_onBlur = function(e) {
	this.hasFocus = false;
	this.closeItems();
}

function debug(s) {
	$("debug").innerHTML = s + "<br/>" + $("debug").innerHTML;
}